#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define MAX_EVENTS 1000
#define BUFFER_SIZE 1024
#define PORT 8080

// 设置文件描述符为非阻塞
int set_nonblocking(int fd) {
    int flags;
    flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl get");
        return -1;
    }

    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) == -1) {
        perror("fcntl set");
        return -1;
    }
    return 0;
}

int main() {
    //大堂经理
    int listen_fd;
    //服务员
    int conn_fd;
    //内核epoll表id
    int epoll_fd;
    //返回就绪的事件数
    int nfds;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    socklen_t server_len = sizeof(server_addr);

    //event用来设置要监听的事件类型和文件描述符
    struct epoll_event event;
    //将来用来接收epoll_wait返回的数组
    struct epoll_event events[MAX_EVENTS];
    //数据缓冲区
    char buffer[BUFFER_SIZE];
    
    // 1. 创建监听套接字
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 2. 设置套接字为非阻塞
    if (set_nonblocking(listen_fd) == -1) {
        exit(EXIT_FAILURE);
    }

    // 3. 绑定IP和端口
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有IP
    server_addr.sin_port = htons(PORT);

    if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }


    // 4. 开始监听
    if (listen(listen_fd, SOMAXCONN) == -1) {
        perror("listen");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }

    // 5. 创建epoll表
    epoll_fd = epoll_create1(0);
    if(epoll_fd < 0){
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    // 6. 把监听套接字加入到epoll表
    event.data.fd = listen_fd;
    event.events = EPOLLIN ; // 只关注读事件
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) == -1) {
        perror("epoll_ctl: listen_sock");
        close(listen_fd);
        close(epoll_fd);
        exit(EXIT_FAILURE);
    }

    printf("server listening on port %d\n", PORT);

    //7.事件轮询
    while(1){
        nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if(nfds < 0){
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }

        for(int i = 0; i <nfds; i ++){
            //接收连接
            if(events[i].data.fd == listen_fd){
                //有新的连接到来，接受这个新连接, tod...
                while(1){
                    conn_fd = accept(listen_fd, 
                        (struct sockaddr*)&client_addr, &client_len);
                    if(conn_fd<0){
                        if(errno == EAGAIN || errno == EWOULDBLOCK){
                            break;
                        }else{
                            perror("accept");
                            break;
                        }
                    }

                    //设置非阻塞
                    if(set_nonblocking(conn_fd) == -1){
                        close(conn_fd);
                        continue;
                    }

                    //把新连接加入到epoll表
                    event.data.fd = conn_fd;
                    event.events = EPOLLIN ; // 读事件和边沿触发模式
                    if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, &event) == -1){
                        perror("epoll_ctl: conn_sock");
                        close(conn_fd);
                        continue;
                    }

                    printf("new connection from %s:%d\n",
                        inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                }
                continue;
            }

            //接收数据
            int result = read(events[i].data.fd, buffer, sizeof(buffer));
            if(result < 0){
                if(errno == EAGAIN || errno == EWOULDBLOCK){
                    continue;
                }

                perror("read");
                close(events[i].data.fd);
                continue;
            }

            if(result == 0){
                printf("client closed connection\n");
                close(events[i].data.fd);
                continue;
            }

            printf("received data from client: %s\n", buffer);

            int write_len = write(events[i].data.fd, buffer, strlen(buffer));
            if(write_len < 0){
                if(errno == EAGAIN || errno == EWOULDBLOCK){
                    continue;
                }

                perror("write");
                close(events[i].data.fd);
                continue;
            }

            if(write_len == 0){
                printf("client closed connection\n");
                close(events[i].data.fd);
                continue;
            }  
          
        }
    }

    close(listen_fd);
    close(epoll_fd);

    return 0;
}